import {
    BadRequestException,
    Inject,
    Injectable,
    Logger,
    UnauthorizedException,
    OnApplicationBootstrap,
    ForbiddenException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { DataSource, In, Repository } from "typeorm";
import { CollectEntity, MediaEntity, RoleEntity, UploadHistoryEntity, UserEntity } from "src/entity";
import { UserQueryDto, UserRemoveDto, UserUpdateDto } from "src/dto/user";
import { ConfigService } from "@nestjs/config";
import { CACHE_MANAGER, Cache } from "@nestjs/cache-manager";
import { Emitter, ROOT_ROLE_ID, ROOT_USER_ID, getUserInfoKey } from "src/constant";
import { pagination } from "src/common/pagination.query";
import { AuthSignUpDto } from "src/dto/auth";
import { WsCommonException } from "src/exception";
import { EventEmitter2 } from "src/common/event2";
import { instanceToPlain } from "class-transformer";

@Injectable()
export class UserService implements OnApplicationBootstrap {
    private readonly logger = new Logger(UserService.name);

    constructor(
        @Inject(CACHE_MANAGER) private cacheManager: Cache,
        private readonly dataSource: DataSource,
        @InjectRepository(UserEntity) private readonly userRepository: Repository<UserEntity>,
        private readonly configService: ConfigService,
        private readonly eventEmitter: EventEmitter2,
    ) {}

    onApplicationBootstrap() {
        return this.initRootAdminUser();
    }

    async initRootAdminUser() {
        await this.dataSource.transaction(async (manager) => {
            let admin = await manager.findOne(UserEntity, {
                where: {
                    id: ROOT_USER_ID,
                },
                relations: {
                    roles: true,
                },
                relationLoadStrategy: "join",
            });

            const username = this.configService.get<string>("ROOT_ADMIN_USER_NAME");

            if (!admin) {
                admin = manager.create(UserEntity, {
                    id: ROOT_USER_ID,
                    username: username,
                    password: this.configService.get<string>("ROOT_ADMIN_USER_PASS"),
                    roles: [],
                });
            }

            if (admin.username != username) {
                this.logger.warn(`root admin user id ${ROOT_USER_ID} name not is ${username}, current is ${admin.username}.`);
            }

            if (admin.roles.find((item) => item.id == ROOT_ROLE_ID)) return;

            admin.roles = [await manager.findOneBy(RoleEntity, { id: ROOT_ROLE_ID }), ...admin.roles];

            await manager.save(admin);
        });
    }

    findByName(username: string) {
        this.logger.log(`find user in name: ${username}`);
        return this.userRepository.findOne({
            where: { username },
            relations: {
                roles: true,
            },
        });
    }

    find(id: number) {
        this.logger.log(`find user in id: ${id}`);
        return this.userRepository.findOne({
            where: { id },
            relations: {
                roles: true,
            },
        });
    }

    async cacheRequsetUser(user: UserEntity) {
        const requsetUser: RequsetUser = {
            id: user.id,
            username: user.username,
            createTime: user.createTime,
            updateTime: user.updateTime,
            roles: [],
            permissions: [],
        };

        requsetUser.roles = user.roles.map((item) => {
            requsetUser.permissions = [...new Set([...item.permissions.map((item) => item.code), ...requsetUser.permissions])];
            return item.name;
        });

        await this.cacheManager.set(getUserInfoKey(user.id), requsetUser);

        return requsetUser;
    }

    async refreshRequsetUser(id: number) {
        const user = await this.userRepository.findOne({
            where: { id },
            relations: {
                roles: {
                    permissions: true,
                },
            },
            relationLoadStrategy: "join",
        });

        if (!user) throw new UnauthorizedException("用户不存在!");

        return await this.cacheRequsetUser(user);
    }

    async getRequestUser(id: number) {
        let requsetUser = await this.cacheManager.get<RequsetUser>(getUserInfoKey(id));
        if (!requsetUser) {
            requsetUser = await this.refreshRequsetUser(id);
        }
        return requsetUser;
    }

    async create({ username, roles, ...user }: Omit<AuthSignUpDto, "password2">) {
        this.logger.log(`create user: ${username}`);

        const newUser = await this.dataSource.transaction(async (manager) => {
            if (await manager.findOneBy(UserEntity, { username })) throw new BadRequestException("用户已存在");

            return await manager.save(
                manager.create(UserEntity, {
                    ...user,
                    username,
                    roles: roles
                        ? await manager.findBy(RoleEntity, {
                              id: In(roles),
                          })
                        : [],
                }),
            );
        });

        this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.user", newUser.id);

        return newUser;
    }

    queryUser(dto: UserQueryDto) {
        this.logger.log("requset user list");
        return pagination(this.userRepository.createQueryBuilder("user").leftJoinAndSelect("user.roles", "roles"), dto);
    }

    async queryUserByIds(...ids: number[]) {
        if (ids.length == 0) return [];
        return instanceToPlain(
            await this.userRepository
                .createQueryBuilder("user")
                .leftJoinAndSelect("user.roles", "roles")
                .where("user.id IN (:...ids)", { ids })
                .getMany(),
        );
    }

    async updateUser(fromUser: RequsetUser, dto: UserUpdateDto) {
        this.logger.log(`requset modify user : ${dto.id} `);

        if (dto.id == ROOT_USER_ID && fromUser.id != ROOT_USER_ID) throw new BadRequestException("拒绝修改超级管理员");

        if (!dto.roles && !dto.password) return;

        const user = await this.dataSource.transaction(async (manager) => {
            const user = await manager.createQueryBuilder(UserEntity, "user").where("user.id = :id", { id: dto.id }).getOne();

            if (!user) throw new BadRequestException("不存在此用户");

            if (dto.password && dto.password != user.password) {
                user.password = dto.password;
            }

            if (dto.roles) {
                if (user.id == ROOT_USER_ID && !dto.roles.includes(ROOT_ROLE_ID))
                    throw new ForbiddenException("拒绝移除超级管理员的admin角色");

                user.roles = await manager.findBy(RoleEntity, {
                    id: In(dto.roles),
                });
            }

            return await manager.save(user);
        });

        await this.cacheManager.del(getUserInfoKey(user.id));

        if (dto.roles) this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.user", user.id);

        return user;
    }

    async remove(user: RequsetUser, dto: UserRemoveDto) {
        this.logger.log(`user ${user.id} requset remove user : ${dto.id} `);

        if (user.id == dto.id) throw new BadRequestException("拒绝删除自己");

        if (dto.id == ROOT_USER_ID) throw new BadRequestException("拒绝删除超级管理员");

        const removeUser = await this.find(dto.id);

        if (!removeUser) throw new BadRequestException("不存在此用户");

        await this.dataSource.transaction(async (manager) => {
            await manager.delete(CollectEntity, { user: { id: dto.id } });
            await manager.delete(MediaEntity, { user: { id: dto.id } });
            await manager.delete(UploadHistoryEntity, { user: { id: dto.id } });
            await manager.delete(UserEntity, dto.id);
        });
        await this.cacheManager.del(getUserInfoKey(dto.id));
        this.eventEmitter.emitAsync(
            Emitter.CLOSE_WS_BY_USER_ID,
            dto.id,
            "",
            new WsCommonException(444, `你被管理员 ${user.username} 删除了`),
        );
        this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "remove.user", dto.id);
    }
}
